package com.lagou.binding;

import com.lagou.pojo.Configuration;
import com.lagou.pojo.MappedStatement;
import com.lagou.pojo.SqlCommandType;
import com.lagou.sqlsession.SqlSession;

import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Collection;

public class MapperMethod {
    private final SqlCommand sqlCommand;
    private final MethodSignature method;

    public MapperMethod(Class<?> mapperInterface, Method method, Configuration configuration) {
        this.sqlCommand = new SqlCommand(mapperInterface, method, configuration);
        this.method = new MethodSignature(mapperInterface, method, configuration);
    }

    public Object execute(Object[] args, SqlSession sqlSession) {
        Object result = null;
        switch (sqlCommand.getType()) {
            case INSERT:
                result = sqlSession.insert(sqlCommand.getStatementId(), args);
                break;
            case UPDATE:
                result = sqlSession.update(sqlCommand.getStatementId(), args);
                break;
            case DELETE:
                result = sqlSession.delete(sqlCommand.getStatementId(), args);
                break;
            case SELECT:
                if (method.isReturnsMany()) {
                    result = executeForMany(args, sqlSession);
                } else {
                    result = executeForOne(args, sqlSession);
                }
                break;
            default:
                throw new BindingException("未知的执行方法" + sqlCommand.getStatementId());
        }
        return result;
    }

    private Object executeForOne(Object[] args, SqlSession sqlSession) {
        return sqlSession.selectOne(sqlCommand.getStatementId(), args);
    }

    private Object executeForMany(Object[] args, SqlSession sqlSession) {
        return sqlSession.selectList(sqlCommand.getStatementId(), args);
    }

    public static class SqlCommand {
        private final String statementId;
        private final SqlCommandType type;

        public SqlCommand(Class<?> mapperInterface, Method method, Configuration configuration) {
            String methodName = method.getName();
            MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, configuration);
            this.statementId = ms.getId();
            this.type = ms.getSqlCommandType();
        }

        public String getStatementId() {
            return statementId;
        }

        public SqlCommandType getType() {
            return type;
        }

        private MappedStatement resolveMappedStatement(Class<?> mapperInterface,
                                                       String methodName,
                                                       Configuration configuration) {
            String statementId = mapperInterface.getName() + "." + methodName;
            return configuration.getMappedStatementMap().get(statementId);
        }

    }

    public static class MethodSignature {
        private final boolean returnsVoid;
        private final boolean returnsMany;
        private final Class<?> returnType;

        public MethodSignature(Class<?> mapperInterface, Method method, Configuration configuration) {
            Type genericReturnType = method.getGenericReturnType();
            if (genericReturnType instanceof Class<?>) {
                this.returnType = (Class<?>) genericReturnType;
            } else if (genericReturnType instanceof ParameterizedType) {
                this.returnType = (Class<?>) ((ParameterizedType) genericReturnType).getRawType();
            } else {
                this.returnType = method.getReturnType();
            }
            this.returnsVoid = Void.class.equals(this.returnType);
            this.returnsMany = Collection.class.isAssignableFrom(this.returnType);
        }

        public boolean isReturnsVoid() {
            return returnsVoid;
        }

        public boolean isReturnsMany() {
            return returnsMany;
        }

        public Class<?> getReturnType() {
            return returnType;
        }
    }
}

